/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package TweetRay;

import processing.core.PVector;

/**
 *
 * @author Matt
 */
public class Film {

    public float[] pixels;
    private int sx = 0;
    private int sy = 0;
    public float brightest = 0;

    //Gaussian reconstruction filter parameters
    float width = 1.5f;
    float a = .18f;
    float alpha = 1;
    float exp = (float) Math.exp(-alpha * width * width);
    float invGamma = 1.f / 2.2f;
    public boolean autoBright = true;

    public void setGamma(float gamma) {
        invGamma = 1.f / gamma;
    }

    public void setAlpha(float alpha) {
        this.a = alpha;
    }

    public void setBright(float bright) {
        brightest = bright;
        autoBright = false;
    }

    public Film(int x, int y) {
        sx = x;
        sy = y;
        pixels = new float[4 * x * y];

        for (int i = 0; i < 4 * x * y; i++) {
            pixels[i] = 0;
        }

    }

    public void addWeighted(int x, int y,
            float offX, float offY,
            PVector accum, float weight) {

        float dx, dy;
        float mWeight;
        for (int i = -2; i <= 2; i++) {
            for (int j = -2; j <= 2; j++) {
                dx = i - offX;
                dy = j - offY;
                //gaussian accumulation
                mWeight = CoreFunctions.max(0.f, (float) (Math.exp(-alpha * dx * dx) - exp));
                mWeight *= CoreFunctions.max(0.f, (float) (Math.exp(-alpha * dy * dy) - exp));


                if (mWeight > 0) {
                    discreteAddWeighted(x + i, y + j, accum, mWeight);
                }
            }
        }
    }

    private void discreteAddWeighted(int x, int y, PVector accum, float weight) {



        if (x >= 0 && y >= 0 && x < sx && y < sy) {
            pixels[4 * y * sx + 4 * x] += accum.x * weight;
            pixels[4 * y * sx + 4 * x + 1] += accum.y * weight;
            pixels[4 * y * sx + 4 * x + 2] += accum.z * weight;
            pixels[4 * y * sx + 4 * x + 3] += weight;
        }

    }

    public float getLuminance(int x, int y) {
        float weight = pixels[4 * y * sx + 4 * x + 3];
        float cx = ((pixels[4 * y * sx + 4 * x] / weight));
        float cy = ((pixels[4 * y * sx + 4 * x + 1] / weight));
        float cz = ((pixels[4 * y * sx + 4 * x + 2] / weight));

        if (weight == 0) {
            return -1;
        }

        return ((cx * .212671f) + (cy * .715160f) + (cz * .072169f));


    }

    private float sRGB(float in) {
        if (in < 0.0031308) {
            return 12.92f * in;
        } else {
            return (float) (1.055f * Math.pow(in, 1.f / 2.4f) - .055f);
        }
    }

    public int toneMap(int x, int y) {


        float weight = pixels[4 * y * sx + 4 * x + 3];

        float cx = ((pixels[4 * y * sx + 4 * x] / weight));
        float cy = ((pixels[4 * y * sx + 4 * x + 1] / weight));
        float cz = ((pixels[4 * y * sx + 4 * x + 2] / weight));


        float Lp = (cx * .212671f + cy * .715160f + cz * .072169f);


        float L = (a * Lp) / brightest;

        float Lw = brightest;

        float scale = L * (1 + L / (Lw * Lw)) / (1 + L);


        cx *= scale;
        cy *= scale;
        cz *= scale;


        //gama correct
        cx = sRGB(cx);
        cy = sRGB(cy);
        cz = sRGB(cz);
        /*
        cx = (float) Math.pow(cx, invGamma);
        cy = (float) Math.pow(cy, invGamma);
        cz = (float) Math.pow(cz, invGamma);
         */


        //scale to the display range
        cx *= 255;
        cy *= 255;
        cz *= 255;


        //vignetting
        float sc;
        float off = sy/2;
        float on = sy/5;
        float fade = .15f;

        sc = (float) Math.sqrt((x - sx / 2) * (x - sx / 2) + (y - sy / 2) * (y - sy / 2));
        if (sc > on) {
            sc -= on;
            sc /= (off - on);
            sc *= sc;


            sc = (1 - (fade * sc));
            if (sc < 0) {
                sc = 0;
            }
            cx *= sc;
            cy *= sc;
            cz *= sc;
        }



        //clamping
        if (cx > 255) {
            cx = 255;
        }
        if (cy > 255) {
            cy = 255;
        }
        if (cz > 255) {
            cz = 255;
        }


        return (((int) cx) << 16 | (int) cy << 8 | (int) cz);


    }
}
